Create dynamic proxies in the same ALC as the interface they implement#1247
Merged
Create dynamic proxies in the same ALC as the interface they implement#1247
Conversation
This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies. The bug here was that we were not supplying a structural `AssemblyName` equality comparer to our `ImmutableHashSet`. Since `AssemblyName.Equals` is a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.
This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures. To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.
There was a problem hiding this comment.
Pull Request Overview
This PR addresses critical issues with dynamic proxy generation in .NET environments using multiple AssemblyLoadContexts (ALCs), ensuring proxies are generated in the correct ALC to prevent type resolution failures.
- Fixes dynamic assembly reuse by implementing proper AssemblyName equality comparison
- Ensures dynamic proxies are generated in the appropriate AssemblyLoadContext to prevent type resolution issues
- Adds comprehensive tests to validate ALC-specific proxy generation behavior
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/UnreachableAssembly/UnreachableAssembly.csproj | Creates test assembly project that will be unreachable from default ALC |
| test/UnreachableAssembly/SomeUnreachableClass.cs | Defines test class for ALC isolation testing |
| test/StreamJsonRpc.Tests/UnreachableAssemblyTools.cs | Provides utilities for ALC testing scenarios |
| test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj | Configures test project to place UnreachableAssembly in hidden directory |
| test/StreamJsonRpc.Tests/JsonRpcProxyGenerationTests.cs | Adds tests for dynamic assembly reuse and ALC-specific proxy generation |
| src/StreamJsonRpc/SkipClrVisibilityChecks.cs | Fixes ImmutableHashSet creation and removes duplicate AssemblyNameEqualityComparer |
| src/StreamJsonRpc/ProxyGeneration.cs | Implements ALC-aware dynamic assembly creation and proper assembly name comparison |
| src/StreamJsonRpc/AssemblyNameEqualityComparer.cs | Extracts AssemblyNameEqualityComparer into separate file for reuse |
| docfx/docs/dynamicproxy.md | Documents ALC considerations and usage patterns for dynamic proxies |
| StreamJsonRpc.sln | Adds UnreachableAssembly project to solution |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
17b9e6f to
5a32ad3
Compare
crmann1
approved these changes
Aug 14, 2025
ekoppel
approved these changes
Aug 14, 2025
davidwrighton
approved these changes
Aug 14, 2025
This was referenced Oct 7, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces significant improvements to how dynamic proxies are generated and loaded in .NET environments with multiple AssemblyLoadContexts (ALCs). The main focus is to ensure that StreamJsonRpc generates proxies in the correct ALC, preventing type resolution issues when interfaces are loaded from different contexts. The changes also add new tests and documentation to validate and explain this behavior.
This change fixes two bugs:
Create dynamic proxies in fewer dynamic assemblies
This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies.
The bug here was that we were not supplying a structural
AssemblyNameequality comparer to ourImmutableHashSet. SinceAssemblyName.Equalsis a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.Generating proxies into the right ALC
This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures.
To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.
We now default to creating dynamic assemblies in the ALC that loaded the first interface rather than into the ALC that loaded StreamJsonRpc.
devdiv-2551415